/**
 * 
 */
package parallel.project.graphics;

import java.awt.Frame;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.media.opengl.*;
import javax.media.opengl.awt.GLCanvas;

import parallel.project.framework.Mazemap;
import parallel.project.graph.Edge;
import parallel.project.graph.Graph;
import parallel.project.graph.Node;
import parallel.project.graph.Path;

import com.jogamp.opengl.util.*;

/**
 * @author aclayton
 *
 */
public class MazeDisplay implements GLEventListener {
	private final double noderadius = .5;
	private final float[] ObstaclesColor = {1,0,0}; 
	private final float[] GraphsColor = {0,1,0};
	private double offsetX = 0.5;
	private double offsetY = 0.5;
	
	private Mazemap map;
	private Path path;
	private Graph graph;
	
	private List<Point> allPathPoints;
	private int currentIndex = 0;
	private boolean drawCurrent = false;

	public MazeDisplay(Mazemap map){
		this.map = map;
		this.path = null;
		this.graph = null;
		
		GLProfile glp = GLProfile.getDefault();
        GLCapabilities caps = new GLCapabilities(glp);
        GLCanvas canvas = new GLCanvas(caps);
        
        final Frame frame = new Frame("AWT Window Test");
        frame.setSize(300, 300);
        frame.add(canvas);
        frame.setVisible(true);
        
        // by default, an AWT Frame doesn't do anything when you click
        // the close button; this bit of code will terminate the program when
        // the window is asked to close
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        
        
        canvas.addGLEventListener(this);
        
        FPSAnimator animator = new FPSAnimator(canvas, 1);
        animator.add(canvas);
        animator.start();
	}
	
	public MazeDisplay(Mazemap map, Graph graph){
		this.map = map;
		this.graph = graph;
		this.path = null;
		
		GLProfile glp = GLProfile.getDefault();
        GLCapabilities caps = new GLCapabilities(glp);
        GLCanvas canvas = new GLCanvas(caps);
        
        final Frame frame = new Frame("AWT Window Test");
        frame.setSize(300, 300);
        frame.add(canvas);
        frame.setVisible(true);
        
        // by default, an AWT Frame doesn't do anything when you click
        // the close button; this bit of code will terminate the program when
        // the window is asked to close
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        
        
        canvas.addGLEventListener(this);
        
        FPSAnimator animator = new FPSAnimator(canvas, 1);
        animator.add(canvas);
        animator.start();
	}
	
	public MazeDisplay(Mazemap map, Path path){
		this.map = map;
		this.graph = null;
		this.path = path;
		
		System.out.println("start:" + map.getStart() + " end:" + map.getEnd());
		path.printPath();
		
		GLProfile glp = GLProfile.getDefault();
        GLCapabilities caps = new GLCapabilities(glp);
        GLCanvas canvas = new GLCanvas(caps);
        
        final Frame frame = new Frame("AWT Window Test");
        frame.setSize(300, 300);
        frame.add(canvas);
        frame.setVisible(true);
        
        // by default, an AWT Frame doesn't do anything when you click
        // the close button; this bit of code will terminate the program when
        // the window is asked to close
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        
        
        canvas.addGLEventListener(this);
        
        FPSAnimator animator = new FPSAnimator(canvas, 10);
        animator.add(canvas);
        animator.start();
	}
	
	public void drawGraph(GL2 gl, Graph g){
		List<Node> nodes = g.getAllNodes();
	
		for(Node n : nodes){
			drawNode(gl, n);
		}
		
		List<Edge> edges = g.getAllEdges();
		for(Edge e : edges){
			drawEdge(gl, e);
		}
		
	}
	
	public void drawNode(GL2 gl, Node e){
		drawNode(gl, e, GraphsColor);
	}
	
	public void drawNode(GL2 gl, Node e, float[] color){
		gl.glBegin(GL.GL_TRIANGLE_FAN);
		gl.glColor3f(color[0], color[1], color[2]);
		gl.glVertex2d(e.getLocation().getX()+offsetX, e.getLocation().getY()+offsetY);
		
		for(double angle = 0; angle < 2*Math.PI+.1; angle += Math.PI/6){
			double tempX = (e.getLocation().getX()+noderadius*Math.cos(angle))+offsetX;
			double tempY = (e.getLocation().getY()+noderadius*Math.sin(angle))+offsetY;
			gl.glVertex2d(tempX, tempY);
		}
		
		gl.glEnd();
	}
	
	public void drawEdge(GL2 gl, Edge e){
		Point first = e.getFirst().getLocation();
		Point second = e.getSecond().getLocation();
		Point connectToFirst;
		Point connectToSecond;
		List<Point> l = e.getLocations();
		
		if (l.size() == 0)
		{
			connectToFirst = second;
			connectToSecond = first;
		}
		else
		{
			if (l.get(0).distance(first) == 1)
			{
				connectToFirst = l.get(0);
				connectToSecond = l.get(l.size() - 1);
			}
			else
			{
				connectToFirst = l.get(l.size() - 1);
				connectToSecond = l.get(0);
			}
		}
		
		gl.glBegin(GL.GL_LINES);
		gl.glColor3f(GraphsColor[0], GraphsColor[1], GraphsColor[2]);
		gl.glVertex2d(first.getX()+offsetX,first.getY()+offsetY);
		gl.glVertex2d(connectToFirst.getX()+offsetX,connectToFirst.getY()+offsetY);
		gl.glEnd();
		gl.glBegin(GL.GL_LINES);
		gl.glColor3f(GraphsColor[0], GraphsColor[1], GraphsColor[2]);
		gl.glVertex2d(second.getX()+offsetX,second.getY()+offsetY);
		gl.glVertex2d(connectToSecond.getX()+offsetX,connectToSecond.getY()+offsetY);
		gl.glEnd();
		
		for(int a = 0; a < l.size()-1; a++){
			gl.glBegin(GL.GL_LINES);
			gl.glColor3f(GraphsColor[0], GraphsColor[1], GraphsColor[2]);
			gl.glVertex2d(l.get(a).getX()+offsetX, l.get(a).getY()+offsetY);
			gl.glVertex2d(l.get(a+1).getX()+offsetX, l.get(a+1).getY()+offsetY);
			gl.glEnd();
			
		}
	}
	
	public void drawMap(GL2 gl, Mazemap map){
		for(Point p : map.getWallPoints()){
			drawWall(gl, p.x, p.y);
		}
	}
	
	public void drawWall(GL2 gl, int x, int y){
		gl.glBegin(GL.GL_TRIANGLES);
		gl.glColor3f(ObstaclesColor[0], ObstaclesColor[1], ObstaclesColor[2]);
		gl.glColor3f(1, 0, 0);
		gl.glVertex2d((double)x, (double)y);
		gl.glVertex2d((double)x+1, (double)y);
		gl.glVertex2d((double)x+1, (double)y+1);
		gl.glEnd();
		gl.glBegin(GL.GL_TRIANGLES);
		gl.glColor3f(ObstaclesColor[0], ObstaclesColor[1], ObstaclesColor[2]);
		gl.glColor3f(1, 0, 0);
		gl.glVertex2d((double)x, (double)y);
		gl.glVertex2d((double)x, (double)y+1);
		gl.glVertex2d((double)x+1, (double)y+1);
		gl.glEnd();
	}
	
	public void drawPath(GL2 gl, Path p){
		List<Node> nodes = p.getAllNodes();
		for(Node n : nodes){
			drawNode(gl, n);
		}
		
		List<Edge> edges = p.getAllEdges();
		for(Edge e : edges){
			drawEdge(gl, e);
		}
		

		if (allPathPoints == null)
			allPathPoints = p.getAllPoints();
		Node here = new Node(allPathPoints.get(currentIndex));
		drawNode(gl,here,new float[]{0,0,1});
		// needed cause display is called twice every repaint
		if (drawCurrent)
		{
			if (currentIndex == allPathPoints.size() - 1)
				currentIndex = 0;
			else
				currentIndex++;
		}
	}


	/**
	 * @param args
	 */
	public static void main(String[] args) {
		if(!(args.length == 2 || args.length == 3)){
			System.out.println("Usage");
			System.out.println("MapDisplay Map mazefile");
			System.out.println("MapDisplay Graph simulationfile graphfile");
			System.out.println("MapDisplay Path simulationfile pathfile");
			System.exit(0);
		}
		
		Mazemap map = null;
		FileInputStream fis;
		ObjectInputStream in;
		
		System.out.println(args[1]);
		try{
			fis = new FileInputStream(args[1]);
			in = new ObjectInputStream(fis);
			map = (Mazemap)in.readObject();
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		
		MazeDisplay mdisplay;
		
		Path path = null;
		Graph graph = null;
		if(args[0].equals("Map")){
			mdisplay = new MazeDisplay(map);
			
		} else if(args[0].equals("Path")){
			try{
				fis = new FileInputStream(args[2]);
				in = new ObjectInputStream(fis);
				path = (Path)in.readObject();
				in.close();
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}
			mdisplay = new MazeDisplay(map, path);
		} else if(args[0].equals("Graph")){
			try{
				fis = new FileInputStream(args[2]);
				in = new ObjectInputStream(fis);
				graph = (Graph)in.readObject();
				in.close();
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}
			mdisplay = new MazeDisplay(map, graph);
		}
	}


	@Override
	public void display(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		
		gl.glMatrixMode(gl.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho (0, map.getWidth(), 0, map.getHeight(), 0, 1);
		gl.glDisable(gl.GL_DEPTH_TEST);
		gl.glMatrixMode (gl.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		gl.glClearColor(1,1,1, 0);
		gl.glClear(GL.GL_COLOR_BUFFER_BIT);
		
		
		drawMap(gl, map);
		if(graph != null)
			drawGraph(gl,graph);
		if(path != null)
		{
			drawCurrent = !drawCurrent;
			drawPath(gl, path);
		}
	}


	@Override
	public void dispose(GLAutoDrawable arg0) {
		// TODO Auto-generated method stub
		
	}


	@Override
	public void init(GLAutoDrawable arg0) {
		// TODO Auto-generated method stub
		
	}


	@Override
	public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3,
			int arg4) {
		// TODO Auto-generated method stub
		
	}

}
